package com.smile67;

import java.util.concurrent.Semaphore;

/**
 * @author 物联网202102 刘棋
 * @since 2023-09-27
 */
public class ProducerConsumer {
    //定义常量
    private static final int BUFFER_SIZE = 4;   //缓冲区容量
    private static final int NUM_PRODUCERS = 3; //3个生成者
    private static final int NUM_CONSUMERS = 2; //2个消费者

    private static Semaphore[] locks = new Semaphore[BUFFER_SIZE];//locks数组用来存储缓冲区的锁
    private static int[] buffer = new int[BUFFER_SIZE];//buffer数组用来存储资源

    //静态代码块进行初始化加载
    static {
        // 我们使用了Semaphore类来实现对缓冲区的访问控制
        // 每个缓冲区都有一个对应的Semaphore对象用于控制对该缓冲区的访问
        // Semaphore对象的初始许可数为1，表示只允许一个线程同时访问该缓冲区
        for (int i = 0; i < BUFFER_SIZE; i++) {
            locks[i] = new Semaphore(1);
        }
    }

    //生产者
    static class Producer implements Runnable {
        @Override
        public void run() {//使用无限循环来模拟生成者的持续生成行为
            while (true) {
                for (int j = 0; j < BUFFER_SIZE; j++) {
                    if (buffer[j] == 0) {//如果缓冲区的值为0，表示找到空缓冲区
                        try {
                            locks[j].acquire();//获取缓冲区的锁，以确保只有一个线程可以访问该缓冲区

                            //再次检查缓冲区的值是否为0，以防止其他线程在获取锁之前已经修改了缓冲区的值。
                            if (buffer[j] < 1) { //限定一个缓冲区中只能存放一个资源
                                //将缓冲区的值加1，表示生产了一个资源。
                                buffer[j]++;//生成资源
                                //打印出生产者生产资源的信息。
                                System.out.println("生产一个资源，放入缓冲区" + j + "中");
                                //释放缓冲区的锁，允许其他线程访问该缓冲区。
                                locks[j].release();//解锁
                                break;
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (j == BUFFER_SIZE - 1) {
                        System.out.println("找不到空缓冲区，等待中...");
                        try {
                            //如果没有找到空缓冲区，则等待2秒钟再进行下一次循环。
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    static class Consumer implements Runnable {
        @Override
        public void run() {////使用无限循环来模拟生成者的持续生成行为
            while (true) {
                for (int n = 0; n < BUFFER_SIZE; n++) {
                    //if (buffer[n] == 1)：如果缓冲区的值为1，表示该缓冲区为满
                    if (buffer[n] == 1) {
                        try {
                            //获取缓冲区的锁，以确保只有一个线程可以访问该缓冲区
                            locks[n].acquire();
                            //再次检查缓冲区的值是否为1，以防止其他线程在获取锁之前已经修改了缓冲区的值
                            if (buffer[n] >= 1) {
                                //将缓冲区的值减1，表示消费了一个资源
                                buffer[n]--;
                                //打印出消费者消费资源的信息。
                                System.out.println("消费一个资源，从缓冲区" + n + "中取出");
                                //释放缓冲区的锁，允许其他线程访问该缓冲区
                                locks[n].release();
                                break;
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (n == BUFFER_SIZE - 1) {//循环一圈后也没找到满缓冲区
                        System.out.println("找不到满缓冲区，等待中...");
                        try {
                            //如果没有找到满缓冲区，则等待2秒钟再进行下一次循环
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    //主方法
    public static void main(String[] args) {
        //创建一个长度为NUM_PRODUCERS（定义的常量：3个）的线程数组，用于存储生产者线程
        Thread[] producers = new Thread[NUM_PRODUCERS];
        //创建一个长度为NUM_CONSUMERS（定义的常量：2个）的线程数组，用于存储消费者线程
        Thread[] consumers = new Thread[NUM_CONSUMERS];

        //使用循环创建NUM_PRODUCERS个 生产者线程
        for (int i = 0; i < NUM_PRODUCERS; i++) {
            //创建一个新的生产者线程，并将其存储在producers数组中。
            producers[i] = new Thread(new Producer());
            //启动生产者线程。
            producers[i].start();
        }
        //使用循环创建NUM_CONSUMERS个消费者线程
        for (int i = 0; i < NUM_CONSUMERS; i++) {
            //创建一个新的消费者线程，并将其存储在consumers数组中。
            consumers[i] = new Thread(new Consumer());
            //启动消费者线程。
            consumers[i].start();
        }
    }
}
